<template>
  <div class="virtual-list" :style="virtualListSize" @scroll="handleScroll">
    <div class="list-container" :style="{ height: containerHeight + 'px' }">
      <div :style="{ transform: `translateY(${offsetY}px)` }">
        <div
          v-for="(item, index) in currentData"
          :key="index"
          :data-index="startIndex + index"
          class="virtual-item"
        >
          <slot
            :item="item"
            :index="startIndex + index"
            :scrolling="isScrolling"
          >
          </slot>
        </div>
      </div>
    </div>
  </div>
</template>

<script setup>
import { computed, ref, watchEffect } from "vue";
const props = defineProps({
  // 数据源
  data: {
    type: Array,
    default: () => [],
  },
  // 容器高度
  height: {
    type: Number,
    default: 200,
  },
  // 容器宽度
  width: {
    type: Number,
    default: 0,
  },
  // 每项高度不传为不固定 TODO: 不定高元素处理？
  itemHeight: {
    type: [Number, null],
    default: null,
  },
  // 显示数量
  visibleCount: {
    type: [Number, null],
    default: null,
  },
  // 缓冲数量
  bufferCount: {
    type: Number,
    default: 5,
  },
  delay: {
    type: Number,
    default: 300,
  },
});
const emits = defineEmits(["scroll", "scrollEnd", "reachBottom"]);
const itemHeights = ref(
  new Array(props.data.length).fill(props.itemHeight || 50)
);
// 展示容器尺寸
const virtualListSize = computed(() => {
  return {
    height:
      typeof props.height === "number"
        ? props.height === 0
          ? "auto"
          : props.height + "px"
        : props.height,
    width:
      typeof props.width === "number"
        ? props.width === 0
          ? "auto"
          : props.width + "px"
        : props.width,
  };
});
// 实际列表高度
const containerHeight = computed(() =>
  itemHeights.value.reduce((a, b) => a + b, 0)
);
// 显示数量
const visibleCount = computed(() => {
  if (props.visibleCount) {
    return props.visibleCount;
  }
  // 固定高度 并且存在高度 没有传递 显示数量自动计算
  if (props.itemHeight && props.height) {
    return Math.ceil(props.height / props.itemHeight);
  } else {
    return 10;
  }
});
// 起始索引
const startIndex = ref(0);
// 结束索引
const endIndex = computed(() =>
  Math.min(
    startIndex.value + visibleCount.value + props.bufferCount,
    props.data.length
  )
);
// 偏移量
const offsetY = computed(() => {
  if (props.itemHeight) {
    return startIndex.value * props.itemHeight;
  }
  // 不定高
  return itemHeights.value
    .slice(0, startIndex.value)
    .reduce((a, b) => a + b, 0);
});
// 当前展示数据
const currentData = computed(() =>
  props.data.slice(startIndex.value, endIndex.value)
);

watchEffect(() => {
  if (endIndex.value >= props.data.length) {
    emits("reachBottom");
  }
});

// 滚动中
const isScrolling = ref(false);
let scrollTimer = null;

function handleScroll(e) {
  clearTimeout(scrollTimer);
  isScrolling.value = true;
  emits("scroll", e);
  scrollTimer = setTimeout(() => {
    isScrolling.value = false;
    const { scrollTop } = e.target;
    // 计算新的起始索引
    let newStartIndex = 0;
    if (props.itemHeight) {
      // 固定高度情况
      newStartIndex = Math.floor(scrollTop / props.itemHeight);
    } else {
      // 不定高度情况，使用估算值
      newStartIndex = Math.floor(scrollTop / 30);
    }
    // 添加缓冲区
    newStartIndex = Math.max(0, newStartIndex - props.bufferCount);
    // 确保不超过数据范围
    newStartIndex = Math.min(
      newStartIndex,
      Math.max(0, props.data.length - visibleCount.value)
    );
    startIndex.value = newStartIndex;
    emits("scrollEnd", newStartIndex, endIndex.value);
  }, props.delay);
}
</script>

<style scoped>
.virtual-list {
  overflow: auto;
  position: relative;
}
.list-container {
  width: 100%;
  position: relative;
}
</style>
